"""WX panel that incapsulates RFB protocol."""

import wx

#from PIL
import Image
# py2app requires me to use `import Image` instead of from PIL import Image`
# if You don't like it too, please, write hate mail to py2app developers.
# (I'm too lazy to do it).

from rfb import RfbClient
from app.util import keys

class RfbDrawer(RfbClient):

    curQuality = 10
    # pixel formats for various qualities
    _pixelFormats = {
        10: {
            "bits-per-pixel": 16,
            "depth": 16,
            "big-endian-flag": 0,
            "true-colour-flag": 1,
            "red-max": 31,
            "green-max": 31,
            "blue-max": 31,
            # not used in true-color mode
            "red-shift": 10,
            "green-shift": 5,
            "blue-shift": 0,
        },
        0: {
            "bits-per-pixel": 8,
            "depth": 8,
            "big-endian-flag": 0,
            "true-colour-flag": 0,
            "red-max": 7,
            "green-max": 7,
            "blue-max": 7,
            # not used in true-color mode
            "red-shift": 0,
            "green-shift": 0,
            "blue-shift": 0,
        },
    }

    # PIL formats for various qualities
    # formats are given in tuples
    # (mode, decoder, *mode_args)
    _pilFormats = {
        10: ("RGB", "raw", "BGR;15", 0, 1),
        0: ("P", "raw", )
    }

    _palette = None
    _externalLogMethod = None

    def __init__(self, surface):
        super(RfbDrawer, self).__init__()
        self.surface = surface
        self._palette = ()

    def decodeImg(self, data, width, height):
        # decode image according to current setup
        _format = self._pilFormats[self.curQuality]
        _mode = _format[0]
        _decoder = _format[1]
        _format = _format[2:]
        _img = Image.fromstring(
            _mode, (width, height), data, _decoder,
            *_format)
        if _mode == "P":
            _img.putpalette(self._palette)
        return _img.convert("RGB")

    def setRawRect(self, rect):
        _x = rect["x-position"]
        _y = rect["y-position"]
        _w = rect["width"]
        _h = rect["height"]
        self.surface.applyImage(self.decodeImg(
            rect["data"], _w, _h), _x, _y)

    def fillRect(self, rect):
        _w = rect["width"]
        _h = rect["height"]
        _pixelCount = _w * _h
        rect["data"] = rect["colour"] * _pixelCount
        # is there a faster way?
        self.setRawRect(rect)

    def getPixelFormat(self):
        """Return required pixel format"""
        _rv = self._pixelFormats[self.curQuality]
        self.activePixelFormat = _rv
        return _rv

    def onFbDimensionsSet(self, width, height):
        super(RfbDrawer, self).onFbDimensionsSet(width, height)
        self.surface.prepareForImage(width, height)

    def keyEvent(self, wxCode, isDown):
        _x11Key = keys.wxToX11(wxCode)
        super(RfbDrawer, self).keyEvent(_x11Key, isDown)

    def onSetColourMapEntries(self, colMap):
        _pal = []
        for _col in colMap["colours"]:
            _pal.extend(map(
                lambda el: int((el / 65535.0) * 255),
                (_col["red"], _col["green"], _col["blue"])
            ))
        self._palette = tuple(_pal)

    def onServerCutText(self, text):
        self.surface.setClipboard(text)

    def setLogMethod(self, fn):
        self._externalLogMethod = fn

    def log(self, msg):
        if self._externalLogMethod:
            self._externalLogMethod(msg)
        else:
            super(RfbDrawer, self).log(msg)

class RfbPanel(wx.Panel):

    drawer = _images = None
    _fbUpdateTimer = _actTimer = _clipboardTimer = None
    _image = None
    _wxImage = _wxBitmap = None
    _oldClipboard = None

    def __init__(self, parent, id=wx.ID_ANY, size=(), pos=()):
        super(RfbPanel, self).__init__(
            parent=parent,
            id=id,
            size=size, pos=pos,
            style=wx.NO_FULL_REPAINT_ON_RESIZE | wx.WANTS_CHARS,
        )
        self.drawer = RfbDrawer(self)

        _timerId = wx.NewId()
        self._actTimer = wx.Timer(self, _timerId)
        self.Bind(wx.EVT_TIMER, self.OnTickleProto, self._actTimer)
        self._actTimer.Start(milliseconds=20, oneShot=False)

        _timerId = wx.NewId()
        self._fbUpdateTimer = wx.Timer(self, _timerId)
        self.Bind(wx.EVT_TIMER, self.OnRequestFbUpdate, self._fbUpdateTimer)
        self._fbUpdateTimer.Start(milliseconds=200, oneShot=False)

        _timerId = wx.NewId()
        self._clipboardTimer = wx.Timer(self, _timerId)
        self.Bind(wx.EVT_TIMER, self.OnClipboardCheck, self._clipboardTimer)
        self._clipboardTimer.Start(milliseconds=200, oneShot=False)

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse)
        self.Bind(wx.EVT_CHAR, self.OnChar)

    @property
    def active(self):
        return self.drawer.active

    def useSocket(self, sock):
        self.drawer.attachToSocket(sock)

    def OnTickleProto(self, evt):
        self.drawer.act()

    def OnRequestFbUpdate(self, evt):
        if not self.active:
            self.Refresh()
            return
        self.drawer.screenUpdateRequest()

    def prepareForImage(self, width, height):
        self._pilImage = Image.new("RGB", (width, height))
        self._wxImage = wx.EmptyImage(width, height)
        self.SetSize((width, height))
        self.SetMaxSize((width, height))

    def applyImage(self, img, x, y):
        self._pilImage.paste(img, (x, y))
        # transferring new image to WX
        self._wxImage.SetData(self._pilImage.tostring())
        self._wxBitmap = wx.BitmapFromImage(self._wxImage)
        self.Refresh(False)

    def OnPaint(self, evt):
        if self._wxBitmap and self.active:
            _dc = wx.BufferedPaintDC(self, buffer=self._wxBitmap)
        else:
            _dc = wx.BufferedPaintDC(self)
            _dc.SetBackground(wx.Brush("BLACK"))
            _dc.Clear()

    def OnMouse(self, evt):
        if not self.active:
            return
        _x = evt.X
        _y = evt.Y
        _rot = evt.GetWheelRotation()
        _buttons = (
	    evt.LeftIsDown(),
    	    evt.MiddleIsDown(),
	    evt.RightIsDown(),
            _rot > 0,
            _rot < 0,
  	)
        if _x >= 0 and _y >= 0:
            self.drawer.mouseEvent(_x, _y, _buttons)
        if any(_buttons):
  	    # KeyEvents are fetched only in this control is focused
  	    self.SetFocus()

    def setClipboard(self, text):
        clipdata = wx.TextDataObject()
        clipdata.SetText(text)
        wx.TheClipboard.Open()
        wx.TheClipboard.SetData(clipdata)
        wx.TheClipboard.Close()

    def OnClipboardCheck(self, evt):
        """Clipboard change chacking timer."""
        if not self.active:
            return
        _data = wx.TextDataObject()
        wx.TheClipboard.Open()
        _success = wx.TheClipboard.GetData(_data)
        wx.TheClipboard.Close()
        if _success:
            _text = _data.GetText()
            if _text != self._oldClipboard:
                self.drawer.clientCutText(_text)
                self._oldClipboard = _text

    def OnChar(self, evt):
        _code = evt.GetKeyCode()
        self.drawer.keyEvent(_code, True)

    def setLogMethod(self, fn):
        self.drawer.setLogMethod(fn)

    def detach(self):
        if not self.active:
            return
        self.drawer.detach()

# vim: set sts=4 sw=4 et :
